/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "lexer.h"

#include <cstring>
#include <iostream>

// Useful macros for the lexer
#define TK2STR( TOKEN ) #TOKEN
#define NEWLINE() line_++;
#define CLEAR() charbuff_.clear();
#define LENGTH() charbuff_.length()
#define STRING() charbuff_.c_str()
#define HEAD() charbuff_[charbuff_.length()-1]
#define PUSH() charbuff_.push_back( cc_ );
#define POP() charbuff_.erase( charbuff_.length()-1 );
#define ISCAPALPHA(c) ( c>64 && c<91 )
#define ISALPHA(c) ( c>96 && c<123 )
#define ISNUMBER(c) ( c>47 && c<58 )
#define COPYSTR() tk.val.s_ = new corelib::ustring(STRING());
#define ERROR(errstr) {errorHnd_->lexError(line_,errstr); \
    throw std::logic_error("lexical error");}
#define NEXT() {cc_ = getCharHnd_->getc(); \
    if( ((unsigned char)cc_) > 0x7F ) unexpected();}
#define NEXT_UTF8() {cc_ = getCharHnd_->getc();}

// token string table
typedef struct {
    // Name of the token type
    const char *name_;
    // String to show in an error message
    const char *tk_;
} token_str_inf;

// number of keywords
const corelib::uint32 nkeywords = 27;

token_str_inf str_token[] = {
    // main keywords
    {TK2STR(TK_BREAK),"break"},
    {TK2STR(TK_CASE),"case"},
    {TK2STR(TK_CATCH),"catch"},
    {TK2STR(TK_CONTINUE),"continue"},
    {TK2STR(TK_DEFAULT),"default"},
    {TK2STR(TK_DO),"do"},
    {TK2STR(TK_ELSE),"else"},
    {TK2STR(TK_EXTENDS),"extends"},
    {TK2STR(TK_FALSE),"false"},
    {TK2STR(TK_FOREACH),"foreach"},
    {TK2STR(TK_FROM),"from"},
    {TK2STR(TK_IF),"if"},
    {TK2STR(TK_IMPORT),"import"},
    {TK2STR(TK_IN),"in"},
    {TK2STR(TK_INSTOF),"instanceof"},
    {TK2STR(TK_NULL),"null"},
    {TK2STR(TK_OPERATOR),"operator"},
    {TK2STR(TK_PACKAGE),"package"},
    {TK2STR(TK_RETURN),"return"},
    {TK2STR(TK_STATIC),"static"},
    {TK2STR(TK_SWITCH),"switch"},
    {TK2STR(TK_THIS),"this"},
    {TK2STR(TK_THROW),"throw"},
    {TK2STR(TK_TRUE),"true"},
    {TK2STR(TK_TRY),"try"},
    {TK2STR(TK_VAR),"var"},
    {TK2STR(TK_WHILE),"while"},
    // eof
    {TK2STR(TK_EOF),"EOF"},
    // number & string
    {TK2STR(TK_CONSTID),"const_id"},
    {TK2STR(TK_CLASSID),"class_id"},
    {TK2STR(TK_ATTRID),"attibute_id"},
    {TK2STR(TK_COMMID),"common_id"},
    {TK2STR(TK_INT),"integer"},
    {TK2STR(TK_REAL),"real"},
    {TK2STR(TK_STRING),"string"},
    // aritmetic operators
    {TK2STR(TK_ADD),"+"},
    {TK2STR(TK_SUB),"-"},
    {TK2STR(TK_MLT),"*"},
    {TK2STR(TK_DIV),"/"},
    {TK2STR(TK_PRC),"%"},
    // urinary tokens
    {TK2STR(TK_DPLUS),"++"},
    {TK2STR(TK_DSUB),"--"},
    // bitwise operators
    {TK2STR(TK_SHFRUS),">>>"},
    {TK2STR(TK_SHFRS),">>"},
    {TK2STR(TK_SHFL),"<<"},
    {TK2STR(TK_AND),"&"},
    {TK2STR(TK_XOR),"^"},
    {TK2STR(TK_NOT),"~"},
    {TK2STR(TK_OR),"|"},
    // assignment tokens
    {TK2STR(TK_SHFRUSEQ),">>>="},
    {TK2STR(TK_SHFRSEQ),">>="},
    {TK2STR(TK_SHFLEQ),"<<="},
    {TK2STR(TK_ADDEQ),"+="},
    {TK2STR(TK_SUBEQ),"-="},
    {TK2STR(TK_MLTEQ),"*="},
    {TK2STR(TK_DIVEQ),"/="},
    {TK2STR(TK_MODEQ),"%="},
    {TK2STR(TK_ANDEQ),"&="},
    {TK2STR(TK_XOREQ),"^="},
    {TK2STR(TK_OREQ),"|="},
    {TK2STR(TK_EQ),"="},
    // logic tokens
    {TK2STR(TK_DAMP),"&&"},
    {TK2STR(TK_DVBAR),"||"},
    {TK2STR(TK_LESST),"<"},
    {TK2STR(TK_GRTT),">"},
    {TK2STR(TK_LESSEQ),"<="},
    {TK2STR(TK_GRTEQ),"=>"},
    {TK2STR(TK_DEQ),"=="},
    {TK2STR(TK_NE),"!="},
    // brackets
    {TK2STR(TK_LCRLB),"{"},
    {TK2STR(TK_RCRLB),"}"},
    {TK2STR(TK_LRNDB),"("},
    {TK2STR(TK_RRNDB),")"},
    {TK2STR(TK_LSQRB),"["},
    {TK2STR(TK_RSQRB),"]"},
    // another tokens
    {TK2STR(TK_DOT),"."},
    {TK2STR(TK_COLON),":"},
    {TK2STR(TK_DCOLON),"::"},
    {TK2STR(TK_SEMC),";"},
    {TK2STR(TK_COM),","},
    {TK2STR(TK_EXCL),"!"},
    {TK2STR(TK_QST),"?"},
    {TK2STR(TK_ARROP),"[]"}
};

namespace rock{

lexer::lexer()
{
    getCharHnd_ = corelib::null;
    errorHnd_ = corelib::null;

    first_ = true; line_ = 1;

    CLEAR();
}

lexer::~lexer()
{

}

void lexer::reset( void )
{
    first_ = true;
    line_ = 1;

    CLEAR();
}

void lexer::setGetCharHnd( get_char_hnd &hnd )
{
    getCharHnd_ = &hnd;
}

void lexer::setErrorHnd( lexer_error_hnd &hnd )
{
    errorHnd_ = &hnd;
}

void lexer::freeTk( token &tk )
{
    switch(tk.type_){
        case TK_STRING:
        case TK_COMMID:
        case TK_ATTRID:
        case TK_CLASSID:
            delete tk.val.s_;
            tk.val.s_ = corelib::null;
    }
}

void lexer::getBOM( void )
{
    // read BOM (Byte Order Mark)
    cc_ = getCharHnd_->getc();
    // check BOM
    if( cc_==0xEF ){
        cc_ = getCharHnd_->getc();
        if( cc_==0xBB ){
            cc_ = getCharHnd_->getc();
            if( cc_==0xBF ){
                NEXT(); // get next character
            }else{
                ERROR("UTF8 Byte-Order-Mark not ended");
            }
        }else{
            ERROR("UTF8 Byte-Order-Mark not ended");
        }
    }
}

token lexer::next( void )
{
    token tk;

    if( first_ ){
        first_ = false;
        getBOM();
    }

    for(;;){
        switch( cc_ ){
            case '/':
                NEXT();
                switch( cc_ ){
                    case '/':
                        getNLComment(); break;
                    case '*':
                        getComment(); break;
                    case '=':
                        NEXT(); tk.type_ = TK_DIVEQ; return tk;
                    default:
                        tk.type_ = TK_DIV; return tk;
                }
                break;
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
            case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
            case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
                getClassId( tk ); return tk;
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':
            case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
            case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
                getRestId( tk ); return tk;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                getNumber( tk ); return tk;
            case '"':
                getString( tk ); return tk;
            case '(':
                NEXT(); tk.type_ = TK_LRNDB; return tk;
            case ')':
                NEXT(); tk.type_ = TK_RRNDB; return tk;
            case '[':
                NEXT();
                if( cc_==']' ){
                    NEXT(); tk.type_ = TK_ARROP; return tk;
                }
                tk.type_ = TK_LSQRB; return tk;
            case ']':
                NEXT(); tk.type_ = TK_RSQRB; return tk;
            case '{':
                NEXT(); tk.type_ = TK_LCRLB; return tk;
            case '}':
                NEXT(); tk.type_ = TK_RCRLB; return tk;
            case '.':
                NEXT(); tk.type_ = TK_DOT; return tk;
            case ',':
                NEXT(); tk.type_ = TK_COM; return tk;
            case ';':
                NEXT(); tk.type_ = TK_SEMC; return tk;
            case '*':
                NEXT();
                if( cc_=='=' ){
                    NEXT(); tk.type_ = TK_MLTEQ; return tk;
                }
                tk.type_ = TK_MLT; return tk;
            case '%':
                NEXT();
                if( cc_=='=' ){
                    NEXT(); tk.type_ = TK_MODEQ; return tk;
                }
                tk.type_ = TK_PRC; return tk;
            case '~':
                NEXT(); tk.type_ = TK_NOT; return tk;
            case '?':
                NEXT(); tk.type_ = TK_QST; return tk;
            case '+':
                NEXT();
                switch(cc_){
                    case '+':
                        NEXT(); tk.type_ = TK_DPLUS; return tk;
                    case '=':
                        NEXT(); tk.type_ = TK_ADDEQ; return tk;
                }
                tk.type_ = TK_ADD; return tk;
            case '-':
                PUSH(); NEXT();
                switch(cc_){
                    case '-':
                        CLEAR(); NEXT(); tk.type_ = TK_DSUB; return tk;
                    case '=':
                        CLEAR(); NEXT(); tk.type_ = TK_SUBEQ; return tk;
                    default:
                        if( ISNUMBER(cc_) ){
                            getNumber( tk );  return tk;
                        }
                }
                CLEAR(); tk.type_ = TK_SUB; return tk;
            case '|':
                NEXT();
                switch(cc_){
                    case '|':
                        CLEAR(); NEXT(); tk.type_ = TK_DVBAR; return tk;
                    case '=':
                        CLEAR(); NEXT(); tk.type_ = TK_OREQ; return tk;
                }
                tk.type_ = TK_OR; return tk;
            case '&':
                NEXT();
                switch(cc_){
                    case '&':
                        CLEAR(); NEXT(); tk.type_ = TK_DAMP; return tk;
                    case '=':
                        CLEAR(); NEXT(); tk.type_ = TK_ANDEQ; return tk;
                }
                tk.type_ = TK_AND; return tk;
            case '<':
                NEXT();
                switch(cc_){
                    case '=':
                        NEXT(); tk.type_ = TK_LESSEQ; return tk;
                    case '<':
                            NEXT();
                        if(cc_=='='){
                            NEXT(); tk.type_ = TK_SHFLEQ; return tk;
                        }else{
                            tk.type_ = TK_SHFL; return tk;
                        }
                }
                tk.type_ = TK_LESST; return tk;
            case '=':
                NEXT();
                switch(cc_){
                    case '>':
                        NEXT(); tk.type_ = TK_GRTEQ; return tk;
                    case '=':
                        NEXT(); tk.type_ = TK_DEQ; return tk;
                }
                tk.type_ = TK_EQ; return tk;
            case '!':
                NEXT();
                if( cc_=='=' ){
                    NEXT(); tk.type_ = TK_NE; return tk;
                }
                tk.type_ = TK_EXCL; return tk;
            case '>':
                NEXT();
                switch(cc_){
                    case '>':
                        NEXT();
                        switch(cc_){
                            case '>':
                                NEXT();
                                if(cc_=='='){
                                    NEXT(); tk.type_ = TK_SHFRUSEQ; return tk;
                                }else{
                                    tk.type_ = TK_SHFRUS; return tk;
                                }
                            case '=':
                                NEXT(); tk.type_ = TK_SHFRSEQ; return tk;
                        }
                        tk.type_ = TK_SHFRS; return tk;
                }
                tk.type_ = TK_GRTT; return tk;
            case ':':
                NEXT();
                if( cc_==':' ){
                    NEXT(); tk.type_ = TK_DCOLON; return tk;
                }
                tk.type_ = TK_COLON; return tk;
            case '\n':
                NEWLINE(); NEXT(); break;
            case '\0':
                eof(); tk.type_ = TK_EOF; return tk;
            default:
                // check unknown character
                if( cc_>32 ){
                    unexpected();
                }
                // get next character
                NEXT();
        }
    }
}

void lexer::getNLComment( void )
{
    bool done = false; NEXT_UTF8();
    // get comment
    while( !done ){
        switch( cc_ ){
			case '\n':
                NEWLINE(); NEXT(); done = true; break;
			case '\0':
                ERROR("new line expected");
                done = true; eof();
            default:
                NEXT_UTF8();
        }
    }
}

void lexer::getComment( void )
{
    bool done = false; NEXT_UTF8();
    while( !done ){
        switch( cc_ ){
			case '*':
                NEXT_UTF8();
                if( cc_=='/' ){
                    NEXT(); done = true;
                }
                break;
			case '\n':
                NEWLINE(); NEXT_UTF8(); break;
			case '\0':
                ERROR("comment unfinished");
                done = true; eof();
            default:
                NEXT_UTF8();
        }
    }
}

void lexer::getNumber( token &tk )
{
    if( cc_=='0' ){
        PUSH(); NEXT();
        switch( cc_ ){
            case '.':{
                PUSH(); NEXT();
                if( !ISNUMBER(cc_) ){
                    ERROR("you have to end a real number with a digit");
                    return;
                }

                while( ISNUMBER(cc_) ){
                   PUSH(); NEXT();
                }

                tk.type_ = TK_REAL;
                std::stringstream ss( STRING() );
                ss >> tk.val.r_; CLEAR();
                return;
            }
            default:
                if( ISNUMBER(cc_) ){
                    ERROR("you cannot put a zero at the left of a number");
                }else{
                    CLEAR(); tk.type_ = TK_INT; tk.val.i_ = 0;
                }
        }
    }else if( ISNUMBER(cc_) ){
        PUSH(); NEXT();
        while( ISNUMBER(cc_) ){
           PUSH(); NEXT();
        }

        if( cc_=='.' ){
            PUSH(); NEXT();
            if( !ISNUMBER(cc_) ){
                ERROR("you have to end a real number with a digit");
                return;
            }

            while( ISNUMBER(cc_) ){
               PUSH(); NEXT();
            }

            tk.type_ = TK_REAL;
            std::stringstream ss( STRING() );
            ss >> tk.val.r_; CLEAR();
            return;
        }

        tk.type_ = TK_INT;
        std::stringstream ss( STRING() );
        ss >> tk.val.i_; CLEAR();
    }
}

void lexer::getString( token &tk )
{
    NEXT_UTF8();

    while( cc_!='"' ){
        if(cc_=='\n'){
            ERROR("you cannot break a string with a new line carry");
            return;
        }
       PUSH(); NEXT_UTF8();
    }

    if( cc_ > 0x7F ){
        unexpected();
    }

    tk.type_ = TK_STRING;
    COPYSTR(); CLEAR();

    NEXT();
}

void lexer::unexpected( void )
{
    std::stringstream ss;
    ss << "unexpected character '" << cc_ << "'";
    ERROR( ss.str().c_str() );
    throw std::exception();
}

void lexer::getClassId( token &tk )
{
    PUSH(); NEXT();

    while( ISCAPALPHA(cc_) || ISALPHA(cc_) || ISNUMBER(cc_) ){
        PUSH(); NEXT();
    }

    tk.type_ = TK_CLASSID;
    COPYSTR(); CLEAR();
}

void lexer::getRestId( token &tk )
{
    PUSH(); NEXT();

    while( ISCAPALPHA(cc_) || ISALPHA(cc_) || ISNUMBER(cc_) ){
        PUSH(); NEXT();
    }

    if( cc_=='_' ){
        PUSH(); NEXT();
        tk.type_ = TK_ATTRID;
        COPYSTR();
    }else{
        tk.type_ = binarySearch( STRING() );
        if( tk.type_==-1 ){
            tk.type_ = TK_COMMID;
            COPYSTR();
        }
    }

    CLEAR();
}

void lexer::eof( void )
{
    line_ = 1;
    CLEAR();
}

std::string lexer::tk2str( const token &tk )
{
    std::stringstream ss; ss << str_token[tk.type_].name_;

    if( tk.type_==TK_CLASSID || tk.type_==TK_COMMID || tk.type_==TK_ATTRID ){
        ss << "( " << *tk.val.s_ << " )";
    }else if( tk.type_==TK_REAL ){
        ss << "( " << tk.val.r_ << " )";
    }else if( tk.type_==TK_INT ){
        ss << "( " << tk.val.i_ << " )";
    }else if( tk.type_==TK_STRING ){
        ss << "( \"" << *tk.val.s_ << "\" )";
    }

    return ss.str();
}

std::string lexer::tkt2str( corelib::uint32 type )
{
    return str_token[type].tk_;
}

corelib::int32 lexer::binarySearch( const char *keyw )
{
    corelib::uint32 low = 0, high = nkeywords;

    while( low < high ){
        corelib::uint32 mid = low + (high-low)/2;

        if( strcmp( str_token[mid].tk_, keyw )<0 ){
            low = mid; low++;
        }else{
            high = mid;
        }
    }

    // high == low, using high or low depends on taste
    if( low<nkeywords && !strcmp( str_token[low].tk_, keyw ) ){
        return low; // found
    }else{
        return -1; // not found
    }
}

}; // namespace rock
